Javascript 闭包(closure)
理解闭包之前,以下概念必须清楚
- 基础数据类型与引用数据类型
- 内存空间
- 垃圾回收机制
- 执行上下文
- 变量对象与活动对象
1 为什么需要闭包?
我们从变量的作用域来进行考究原因
javascript中变量作用域分为两种,一种是全局作用域,一种是函数作用域(ES6中新增块级作用域)
1.1 函数内部可以访问函数外部的变量,即可以直接读取全局变量
|
|
1.2 函数外部却无法直接访问函数内部用var声明的函数内部变量
|
|
2.既然有需求,那么就要解决?什么是闭包?
闭包,官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
啰里啰嗦,抽离靶向的,何必相互为难,
- 闭包就是一个函数; OK了,
- 再进一步,这个函数可以用来获取另外一个函数内部的变量
2.1闭包的特点:
2.1.1 作为一个函数变量的一个引用,当函数返回时,其处于激活状态。
2.1.2 一个闭包就是当一个函数返回时,一个没有释放资源的栈区。简单的说,Javascript允许使用内部函数—即函数定义和函数表达式 位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量 、参数 和声明的其他内部函数 。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。
2.1.3 闭包的作用 : 一个是可以读取函数内部的变量;(局部变量) ,另一个就是让这些变量的值始终保持在内存中 。
2.1.3 闭包形成的条件:
- 在一个函数内部有一个新的函数
- 这个新的函数访问了外部函数内的变量
2.2 闭包栗子
2.2.1 如下一段代码,getInnerNum 可以访问 getNum 函数内部所有的变量,参数,以及函数的值,getInnerNum被getNum函数包住了
|
|
2.2.2 那么如果我们想要getNum内部的变量,该如何访问呢?既然getInnerNum可以访问num变量,不如将这个函数作为返回值
|
|
注意区分下面这段代码
|
|
2.2.3 明确垃圾回收机制,函数体执行后,函数内部声明的变量 ,在函数调用完毕之后,被垃圾回收机制(garbage collection) 回收; 如果
situation 1 : 闭包的作用 一个是可以读取函数内部的变量;(局部变量) ,另一个就是让这些变量的值始终保持在内存中
|
|
|
|
situation 2 :闭包的作用 一个是可以读取函数内部的变量;(局部变量) ,另一个就是让这些变量的值始终保持在内存中
|
|
此时,我们将getNum()的返回值getInnerNum函数给到变量result,result被执行了两次,第一次输出了 200 第二次输出201;这是为什么呢?
原因就在于 getNum 是 getInnerNum 的父函数,而 getInnerNum 被赋给了一个全局变量result,这导致 getInnerNum 始终在内存中,而 getInnerNum 的存在依赖于 getNum ,因此 getNum 也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
因为result引用了getInnerNum.而 getInnerNum又是依赖于getNum,所以result间接引用了外部函数,所以getNum会一直在内存中存在,不会被垃圾回收机制回收;那么其所形成的作用域链也会一直存在;
同时,函数在执行的过程中动态为函数内部变量分配的内存也会一直存在;
|
|
我们需要了解垃圾回收机制
- 对于局部变量,只在函数执行的时候存在,函数运行完毕,局部变量 就会被垃圾回收机制回收;
- 对于全局变量,垃圾回收机制则很难判断什么时候可回收
- 局部变量只在函数的执行过程中存在,函数执行过程中 会为局部变量在栈或堆 上分配相应的空间,以存储它们的值,然后再函数中使用这些变量,直至函数结束
- 但是在闭包中,由于内部函数getInnerNum 被赋值给了全局变量result,所以getNum函数并不算结束,所以垃圾回收机制不会将变量回收,所以函数中的变量 n 是一直存在于内存中的,并没有被回收
- 函数的执行上下文,在执行完毕之后,生命周期结束,那么该函数的执行上下文就会失去引用。其占用的内存空间很快就会被垃圾回收器释放。可是闭包的存在,会阻止这一过程。
这段代码中另一个值得注意的地方,就是
|
|
这一行,首先在addNum前面没有使用var关键字,因此addNum是一个全局变量,而不是局部变量。其次,addNum的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以addNum相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
situation3 : 看下立即执行函数如何利用闭包
|
|
situation 4 数组中的元素引用,形成闭包
|
|
situation5 setTimeout(fn,time) 定时器函数的执行:按道理来说,既然fn被作为参数传入了setTimeout中,那么fn将会被保存在setTimeout变量对象中,setTimeout执行完毕之后,它的变量对象也就不存在了。可是事实上并不是这样。它仍然是存在的。这正是因为闭包。
很显然,这是在函数的内部实现中,setTimeout通过特殊的方式,保留了fn的引用,让setTimeout的变量对象,并没有在其执行完毕后被垃圾收集器回收。因此setTimeout执行结束后经过time时间后,会自动执行fn函数。
|
|
如何输出1,2,3,4,5呢?
|
|
|
|
3 如何避免闭包?
闭包的作用:一个是可以读取函数内部的变量;(局部变量) ,另一个就是让这些变量的值始终保持在内存中
第一个作用使我们想要的,但是第二个会使闭包一直占据内存,这个是我们应该尽力去避免的;由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
4 看下面这行代码的输出: this改变了函数执行的上下文
|
|
5 此时回过头来理解下文章开始的一些基础的定义,以及重新回忆下链式作用域,变量的取值(就近原则),全局作用域,局部作用域
完结
vvv